import re
import os.path

from paste.urlmap import URLMap
from paste.httpexceptions import HTTPUnauthorized
from paste.response import replace_header
from paste.request import parse_dict_querystring, construct_url
from httpencode import HTTP
import simplejson

from jsonstore.store import JSONStore

from webskine.template import JsonTemplate


# paste.composite_factory
def webskine_factory(loader, global_conf, dsn, **local_conf):
    if 'page_cache' in local_conf:
        local_conf['page_cache'] = loader.get_filter(
                local_conf['page_cache'], global_conf=global_conf)
    return Webskine(dsn, **local_conf)


class Webskine(object):
    def __init__(self, dsn, owner=None, default_store='entries', httplib2_cache=None, page_cache=None):
        self.store = InfiniteStore(dsn, owner, default_store, httplib2_cache)
        app = Formatter(self.store, httplib2_cache)
        if page_cache: app = page_cache(app)
        self.pages = app

    def __call__(self, environ, start_response):
        if self.should_filter(environ):
            return self.pages(environ, start_response)
        else:
            return self.store(environ, start_response)

    def should_filter(self, environ):
        return not re.search("(text|application)/json", environ.get("HTTP_ACCEPT"))


class Formatter(object):
    def __init__(self, app, cache_dir):
        self.app = app

        # Create an instance connector for internal requests and cache handling.
        self.http = HTTP(cache=cache_dir)

    def __call__(self, environ, start_response):
        # Copied almost verbatim from ``WSGIFilter.filter.Filter``.
        shortcutted = []
        captured = []
        written_output = []
        def replacement_start_response(status, headers, exc_info=None):
            if exc_info is not None and shortcutted:
                raise exc_info[0], exc_info[1], exc_info[2]
            # Otherwise we don't care about exc_info...
            captured[:] = [status, headers]
            return written_output.append
        app_iter = self.app(environ, replacement_start_response)
        if shortcutted:
            # We chose not to filter
            return app_iter
        if not captured or written_output:
            # This app hasn't called start_response We can't do
            # anything magic with it; or it used the start_response
            # writer, and we still can't do anything with it
            try:
                for chunk in app_iter:
                    written_output.append(chunk)
            finally:
                if hasattr(app_iter, 'close'):
                    app_iter.close()
            app_iter = written_output
        try:
            return self.filter_output(
                environ, start_response,
                captured[0], captured[1], app_iter)
        finally:
            if hasattr(app_iter, 'close'):
                app_iter.close()

    def filter_output(self, environ, start_response, status, headers, data):
        """Apply template to output.

        Here we apply a template to render the requested resource.

        """
        location = construct_url(environ, with_query_string=False, with_path_info=False)
        root, store = location.rsplit('/', 1)

        # Get the template of the name to apply.
        query = parse_dict_querystring(environ)
        name = query.get('t', store)

        # Try to read template.
        uri = '/templates/%s' % name
        try:
            template = self.http.GET(root + uri, output="python", wsgi_request=environ)
        except:
            template = None

        if template:
            # Get JSON entry as a Python object.
            if data.decoded[0] == 'python': data = data.decoded[1]
            else: data = simplejson.loads(''.join(data))

            # Apply template. First the additional parameters...
            t = JsonTemplate(template['content'], self.http, environ)
            options = {"WEBSKINE_ROOT": root, "STORE_LOCATION": location}
            t = t.process(options, remove_empty_nodes=False)
            # ...and now the actual JSON data.
            escape = template.get('escape', True)
            data = [t.process(data, escape=escape).prettify()]

            # Fix headers.
            replace_header(headers, 'content-type', template.get('content_type', 'text/html'))

        start_response(status, headers)
        return data


class InfiniteStore(URLMap):
    def __init__(self, dsn, owner, default_store, cache_dir):
        URLMap.__init__(self)
        self.dsn = dsn
        self.owner = owner
        self.default_store = default_store

        # Create an instance connector for internal requests and cache handling.
        self.http = HTTP(cache=cache_dir)

    def __call__(self, environ, start_response):
        # Check auth.
        if not self.authorized(environ): raise HTTPUnauthorized
        return URLMap.__call__(self, environ, start_response)

    def authorized(self, environ):
        """Simple auth based on openid.
        
        GET/HEAD is always allowed, and all methods are allowed for the
        blog owner. POST is allowed on public stores, and PUT/DELETE are
        allowed if the user is the owner of the resource.

        """
        user = environ.get('REMOTE_USER')
        method = environ['REQUEST_METHOD']

        # If the user is the owner or if the request is without side 
        # effects we allow it.
        if self.owner is None or user == self.owner or method in ['GET', 'HEAD']:
            return True

        # For a POST we need to check if the store being POSTed to is
        # public, ie, if the name starts with ``pub_``.
        elif method == 'POST':
            location = construct_url(environ, with_query_string=False, with_path_info=False)
            root, store = location.rsplit('/', 1)
            return store.startswith('pub_')

        # For a PUT/DELETE, the user must be the owner of the resource.
        # We check for the __owner__ attribute.
        elif method in ['PUT', 'DELETE']:
            url = construct_url(environ, with_query_string=False, with_path_info=True)
            resource = self.http.GET(url, output="python", wsgi_request=environ)
            owner = resource.get('__owner__')
            return user == owner

    def not_found_app(self, environ, start_response):
        path_info = environ.get('PATH_INFO')
        collection = path_info.lstrip('/')
        try:
            # Collection should have a trailing slash, followed
            # by an optional id: collection/[id]
            collection, id_ = collection.split('/', 1)
        except ValueError:
            # Fallback to default store.
            collection = self.default_store
            path_info = '/' + collection + path_info

        app_url = '/' + collection
        storage = os.path.join(self.dsn, collection)
        jsonstore = JSONStore(storage)
        app = self.setdefault(app_url, jsonstore)

        environ['SCRIPT_NAME'] += app_url
        environ['PATH_INFO'] = path_info[len(app_url):]

        return app(environ, start_response)
